/**
 * 
 */
package org.swing.utility.common.thread;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;

import org.swing.utility.common.imp.DoneChecker;
import org.swing.utility.common.imp.IAtomicInteger;


/**
 * @author lqnhu
 *
 */
public class ThreadUtil {

	public static void sleep(long millis, boolean ignoreException) {
		try {
			Thread.sleep(millis);
		} catch (InterruptedException e) {
			if (!ignoreException) {
				throw new RuntimeException(e);
			}
		}
	}

	public static void interruptThread(Thread ithr, int iwaitMs) {
		try {
			Thread.sleep(iwaitMs);
		} catch (InterruptedException e) {
		} finally {
			if (ithr != null && ithr.isAlive() && !ithr.isInterrupted()) {
				ithr.interrupt();
			}
		}
	}

	public static void waiteWithin(final Object ilock, int itmout, int iintvl,
			DoneChecker idonechk) throws InterruptedException {
		int _tmoutresst = itmout;
		int _intvl = iintvl;
		while (!idonechk.isDone() && !idonechk.isCancelled()
				&& (_tmoutresst -= _intvl) > 0) {
			synchronized (ilock) {
				ilock.wait(_tmoutresst > _intvl ? _intvl : _tmoutresst);
			}
		}
	}

	public static void waiteWithin(final Object ilock, int itmout, int iintvl,
			Future idonechk) throws InterruptedException {
		int _tmoutresst = itmout;
		int _intvl = iintvl;
		while (!idonechk.isDone() && !idonechk.isCancelled()
				&& (_tmoutresst -= _intvl) > 0) {
			synchronized (ilock) {
				ilock.wait(_tmoutresst > _intvl ? _intvl : _tmoutresst);
			}
		}
	}

	/**
	 * Gets the "root" thread group for the entire JVM. Internally, first get
	 * the current thread and its thread group. Then get its parent group, then
	 * its parent group, and on up until you find a group with a null parent.
	 * That's the root ThreadGroup. Since the same root thread group is used for
	 * the life of the JVM, you can safely cache it for faster future use.
	 * 
	 * @return The root thread group for the JVM
	 */
	static public ThreadGroup getRootThreadGroup() {
		ThreadGroup tg = Thread.currentThread().getThreadGroup();
		ThreadGroup ptg;
		while ((ptg = tg.getParent()) != null)
			tg = ptg;
		return tg;
	}

	/**
	 * Gets all threads in the JVM. This is really a snapshot of all threads at
	 * the time this method is called.
	 * 
	 * @return An array of all threads currently running in the JVM.
	 */
	static public Thread[] getAllThreads() {
		final ThreadGroup root = getRootThreadGroup();
		final ThreadMXBean thbean = ManagementFactory.getThreadMXBean();
		int nAlloc = thbean.getThreadCount();
		int n = 0;
		Thread[] threads;
		do {
			nAlloc *= 2;
			threads = new Thread[nAlloc];
			n = root.enumerate(threads, true);
		} while (n == nAlloc);
		return java.util.Arrays.copyOf(threads, n);
	}

	/**
	 * Gets a thread by its assigned ID. Internally, this method calls
	 * getAllThreads() so be careful to only call this method once and cache
	 * your result.
	 * 
	 * @param id
	 *            The thread ID
	 * @return The thread with this ID or null if none were found
	 */
	static public Thread getThread(final long id) {
		final Thread[] threads = getAllThreads();
		for (Thread thread : threads)
			if (thread.getId() == id)
				return thread;
		return null;
	}

	/**
	 * Gets all threads if its name matches a regular expression. For example,
	 * using a regex of "main" will execute a case-sensitive match for threads
	 * with the exact name of "main". A regex of ".*main.*" will execute a case
	 * sensitive match for threads with "main" anywhere in their name. A regex
	 * of "(?i).*main.*" will execute a case insensitive match of any thread
	 * that has "main" in its name.
	 * 
	 * @param regex
	 *            The regular expression to use when matching a threads name.
	 *            Same rules apply as String.matches() method.
	 * @return An array (will not be null) of all matching threads. An empty
	 *         array will be returned if no threads match.
	 */
	static public Thread[] getAllThreadsMatching(final String regex) {
		if (regex == null)
			throw new NullPointerException("Null thread name regex");
		final Thread[] threads = getAllThreads();
		ArrayList<Thread> matchingThreads = new ArrayList<Thread>();
		for (Thread thread : threads) {
			if (thread.getName().matches(regex)) {
				matchingThreads.add(thread);
			}
		}
		return matchingThreads.toArray(new Thread[0]);
	}

	/**
	 * Parallel execution of the <code>tasks</code>. The startup is synchronized
	 * so this method emulates a load test.
	 * 
	 * @param tasks
	 *            the list of tasks to be run
	 * @param threadPoolSize
	 *            the size of the parallel threads to be used to execute the
	 *            tasks
	 * @param timeout
	 *            <b>CURRENTLY NOT USED</b> (a maximum timeout to wait for tasks
	 *            finalization)
	 * @param triggerAtOnce
	 *            <tt>true</tt> if the parallel execution of tasks should be
	 *            trigger at once
	 */
	public static final void execute(List<? extends Runnable> tasks,
			int threadPoolSize, long timeout, boolean triggerAtOnce) {
		final CountDownLatch startGate = new CountDownLatch(1);
		final CountDownLatch endGate = new CountDownLatch(tasks.size());

		ExecutorService pooledExecutor = Executors
				.newFixedThreadPool(threadPoolSize);
		for (final Runnable task : tasks) {
			try {
				pooledExecutor.execute(new CountDownLatchedRunnable(task,
						endGate, triggerAtOnce ? startGate : null));
			} catch (RejectedExecutionException reex) {
				; // this should never happen as we submit all tasks at once
			}
		}
		try {
			startGate.countDown();
			endGate.await();
			pooledExecutor.shutdown();
		} catch (InterruptedException e) {
			Thread.currentThread().interrupt();
		}
	}

	/**
	 * Returns a readable name of the current executing thread.
	 */
	public static final String currentThreadInfo() {
		Thread thread = Thread.currentThread();
		return String.valueOf(thread.getName() + "@" + thread.hashCode());
	}

	public static final IAtomicInteger createAtomicInteger(int initialValue) {
		return new AtomicIntegerAdapter(initialValue);
	}

	/*
	 * private static final IPooledExecutor createPooledExecutor(int size) {
	 * return new PooledExecutorAdapter(size); }
	 */

	/**
	 * A special <code>Runnable</code> that uses <code>CountDownLatch</code>-s
	 * to sync on start and to ackowledge its finish.
	 */
	private static class CountDownLatchedRunnable implements Runnable {
		private final Runnable m_task;
		private final CountDownLatch m_startGate;
		private final CountDownLatch m_endGate;

		public CountDownLatchedRunnable(Runnable task, CountDownLatch endGate) {
			this(task, endGate, null);
		}

		public CountDownLatchedRunnable(Runnable task, CountDownLatch endGate,
				CountDownLatch startGate) {
			m_task = task;
			m_startGate = startGate;
			m_endGate = endGate;
		}

		public void run() {
			if (null != m_startGate) {
				try {
					m_startGate.await();
				} catch (InterruptedException iex) {

					Thread.currentThread().interrupt();
					return;
				}
			}

			try {
				m_task.run();
			} finally {
				m_endGate.countDown();
			}
		}
	}

}
